/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package db;

/**
 *
 * @author avasquez
 */
import java.sql.Connection;
import java.sql.Statement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import javax.swing.table.AbstractTableModel;

// ResultSet rows and columns are counted from 1 and JTable
// rows and columns are counted from 0. When processing
// ResultSet rows or columns for use in a JTable, it is
// necessary to add 1 to the row or column number to manipulate
// the appropriate ResultSet column (i.e., JTable column 0 is
// ResultSet column 1 and JTable row 0 is ResultSet row 1).
public class ResultSetTableModel extends AbstractTableModel {

    private Connection connection;
    private Statement statement;
    private ResultSet resultSet;
    private ResultSetMetaData metaData;
    private int numberOfRows;
    // keep track of database connection status 
    private boolean connectedToDatabase = false;
    private String[] nombre_columnas;
    private boolean getNombresReales = true;
    private boolean[] columnas_editables;
    private String SQL = "";
    String nombre_tabla = "";
    String consualtaAct = "";
    boolean actualizacionPreparada = false;
    int colAct[] = null;

    /**Este constructor inicializa el ResultSet y obtiene su metadata para determinar el numero
     * de filas
     * @param conn una coneccion a una base de datos
     * @param query una consulta sql
     * @throws java.sql.SQLException
     */
    public ResultSetTableModel(Connection conn, String query) throws SQLException {
        // load database driver class

        // connect to database
        connection = conn;
        // create Statement to query database   
        statement = connection.createStatement(ResultSet.TYPE_SCROLL_SENSITIVE, ResultSet.CONCUR_READ_ONLY);

        // update database connection status
        connectedToDatabase = true;
        // set query and execute it
        SQL = query;
        setQuery(SQL);
        columnas_editables = new boolean[getColumnCount()];
        for (int i = 0; i < columnas_editables.length; i++) {
            columnas_editables[i] = false;
        }
    } // end constructor ResultSetTableModel

    public void setConecction(Connection cc) {
        connection = cc;
    }

    /**Obtiene la clase Java representativa del
     * modelo de objetos de la base de datos
     * @param column el indice de la comulna
     * @return la clase del objeto
     * @throws java.lang.IllegalStateException
     */
    @Override
    public Class getColumnClass(int column) throws IllegalStateException {
        // ensure database connection is available                        
        if (!connectedToDatabase) {
            throw new IllegalStateException("Not Connected to Database");
        }

        // determine Java class of column
        try {
            String className = metaData.getColumnClassName(column + 1);

            // return Class object that represents className
            return Class.forName(className);
        } // end try
        catch (Exception exception) {
            exception.printStackTrace();
        } // end catch

        return Object.class; // if problems occur above, assume type Object
    } // end method getColumnClass

    /**Obtiene el numero de columnas de la tabla 
     * @return numero de columnas de la tabla
     * @throws java.lang.IllegalStateException
     */
    public int getColumnCount() throws IllegalStateException {
        // ensure database connection is available
        if (!connectedToDatabase) {
            throw new IllegalStateException("Not Connected to Database");
        }

        // determine number of columns
        try {
            return metaData.getColumnCount();
        } // end try
        catch (SQLException sqlException) {
            sqlException.printStackTrace();
        } // end catch

        return 0; // if problems occur above, return 0 for number of columns
    } // end method getColumnCount

    /**Obtiene el nombre de una columna en el ResultSet
     * 
     * @param column indice de la columna
     * @return nombre de la columna
     * @throws java.lang.IllegalStateException
     */
    @Override
    public String getColumnName(int column) throws IllegalStateException {
        // ensure database connection is available
        if (!connectedToDatabase) {
            throw new IllegalStateException("Not Connected to Database");
        }

        // determine column name
        try {
            if (getNombresReales) {
                return metaData.getColumnName(column + 1);
            } else {
                return nombre_columnas[column];
            }
        } // end try
        catch (SQLException sqlException) {
            sqlException.printStackTrace();
        } // end catch

        return "no found"; // if problems, return empty string for column name
    } // end method getColumnName

    /**Establece que en el encabezado de la tabla no debe mostrarse el nombre real
     * del campo sino que los nombres que se reciben como parametros 
     * @param s arreglo de String que se desea ver como encabezado
     */
    public void setThisColumnNames(String[] s) {
        nombre_columnas = s;
        getNombresReales = false;
    }

    /**Obtiene el numero de registros en el ResultSet
     * 
     * @return numero de registros
     * @throws java.lang.IllegalStateException
     */
    public int getRowCount() throws IllegalStateException {
        // ensure database connection is available
        if (!connectedToDatabase) {
            throw new IllegalStateException("Not Connected to Database");
        }

        return numberOfRows;
    } // end method getRowCount

    /**Obtiene el valor continido en una celda
     * 
     * @param row fila
     * @param column columna
     * @return objeto contenido en esa celda
     * @throws java.lang.IllegalStateException
     */
    public Object getValueAt(int row, int column) throws IllegalStateException {
        // ensure database connection is available

        if (!connectedToDatabase) {
            throw new IllegalStateException("Not Connected to Database");
        }

        // obtain a value at specified ResultSet row and column
        try {
            resultSet.absolute(row + 1);
            return resultSet.getObject(column + 1);
        } // end try
        catch (SQLException sqlException) {
            sqlException.printStackTrace();
        } // end catch

        return ""; // if problems, return empty string object
    } // end method getValueAt

    /**Establece si esa celda es editable
     * 
     * @param row fila 
     * @param col columna
     * @return es editable o no
     */
    @Override
    public boolean isCellEditable(int row, int col) {
        return columnas_editables[col];
    }

    /**Establece el nombre de la tabla. Esta es necesario si se desea actualizar un valor en la tabla
     * 
     * @param tabla nombre de la tabla
     */
    public void setTableName(String tabla) {
        nombre_tabla = tabla;
    }

    /**Obtiene el nombre de la tabla
     * 
     * @return nombre de la tabla
     */
    public String getTableName(int columna) {
        String ntabla = "";
        try {
            //System.out.println(metaData.getTableName(columna));
            ntabla = metaData.getTableName(columna);
            nombre_tabla = ntabla;
        } catch (SQLException ex) {
            System.out.println(ex);
        }
        return ntabla;
    }

    /**Este metodo lo utilizaremos para establecer el comportamiento de la actualizacion de
     * la tablita<br>
     * Un ejemplo:<br>
     * sql="Select carnet,nombreAlumno(carnet),act1,act2,exa from notas";<br>
     * tablita.setModelFromSql(sql);<br>
     * sql="Update notas set act1=?,act2=?,exa=? WHERE carnet=? and anio=2008 and mes=2";<br>
     * int cc[] = new int[2,3,4,0];<br>
     * tablita.setActualizacionPreparada(sql,"carnet",cc);<br>
     * Vaya, si se fijan se pones signo de interrogacion en los campos donde se desea actualizar y el la llave, luego en
     * el array de enteros se pasa (en orden!! y comenzando de cero) las columnas que hacen par con los '?'
     * si se fijan el primer ? aparece en act1 y el la tabla es la columna 2 (la tercera)
     * @param consultap consulta de tipo Update con formato preparado
     * @param col_llave el nombre de la columna llave (Debe estar representada en la tabla)
     * @param col_act un arreglo de que representa las columnas que representan los parametros de la consulta
     *
     */
    public void setActualizacionPreparada(String consultap, int col_act[]) {
        consualtaAct = consultap;
        colAct = col_act;
        actualizacionPreparada = true;
    }

    /**Establece un valor en la celda especificada
     * 
     * @param aValue valor a ingresar
     * @param row fila 
     * @param column columna comienza desde 0
     */
    @Override
    public void setValueAt(Object aValue, int row, int column) {
        //javax.swing.JOptionPane.showMessageDialog(null, aValue.toString());
        if (!aValue.equals(getValueAt(row, column))) {
            String tabla = null;
            String sql = null;
            boolean cambiar = false;
            try {
                //tabla = metaData.getTableName(1);
                if (!getNombresReales) {
                    getNombresReales = !getNombresReales;
                    cambiar = true;
                }
                if (actualizacionPreparada) {
                    Object vv = null;
                    java.sql.PreparedStatement ps = connection.prepareStatement(consualtaAct);
                    for (int i = 0; i < colAct.length; i++) {
                        if (colAct[i] == column) {
                            vv = aValue;
                        } else {
                            vv = getValueAt(row,colAct[i]);
                        }
                        System.out.println(i+" "+colAct[i]+" "+vv.toString()+" "+getColumnClass(colAct[i]).getSimpleName());
                        if (getColumnClass(colAct[i]).getSimpleName().compareTo("String") == 0) {
                            ps.setString(i + 1, vv.toString());
                        }
                        if(getColumnClass(colAct[i]).getSimpleName().compareTo("Double") == 0) {
                            ps.setDouble(i + 1, Double.valueOf(vv.toString()));
                        }
                        if (getColumnClass(colAct[i]).getSimpleName().compareTo("Float") == 0) {
                            ps.setFloat(i + 1, Float.valueOf(vv.toString()));
                        }
                        if (getColumnClass(colAct[i]).getSimpleName().compareTo("Integer") == 0) {
                            ps.setInt(i + 1, Integer.valueOf(vv.toString()));
                        }
                    }
                    System.out.println(row + " " + column + " " + ps.toString());
                    ps.executeUpdate();

                } else {
                    //System.out.println("column " + column + " totcols " + row);
                    sql = "UPDATE " + getTableName(column + 1) + " SET " + getColumnName(column) + "=" + ponerComilla(column) + aValue + ponerComilla(column) + " WHERE (";
                    for (int i = 0; i < getColumnCount(); i++) {
                        if (i != column) {
                            sql += " " + getColumnName(i) + "=" + ponerComilla(i) + getValueAt(row, i) + ponerComilla(i);
                            sql += " AND";
                        }
                    }
                    sql = sql.substring(0, sql.length() - 4) + ")";
                    System.out.println(sql);
                    connection.createStatement().executeUpdate(sql);
                    if (cambiar) {
                        getNombresReales = !getNombresReales;
                    }
                }
                setQuery(SQL);
                fireTableCellUpdated(row, column);
            } catch (SQLException ex) {
                System.out.println(ex);
            }
        }
    }

    /**Metodo utilitario que verifica si el objeto es String para ponerle comillas a la consulta
     * 
     * @param column
     * @return comillas
     */
    private String ponerComilla(int column) {
        String ret = "";
        if (getColumnClass(column).getSimpleName().compareTo("String") == 0) {
            ret = "'";
        }
        return ret;
    }

    /**Este metodo comienza con indice 0
     * 
     * @param columna indice de columna
     * @param editable 
     */
    public void setColumnEditable(int columna, boolean editable) {
        if (columna < columnas_editables.length) {
            columnas_editables[columna] = editable;
        }
    }

    /**Establece un conjunto de columnas editables
     * 
     * @param columnas arreglo de indices
     * @param editable
     */
    public void setColumnsEditable(int[] columnas, boolean editable) {
        for (int i = 0; i < columnas.length; i++) {
            setColumnEditable(columnas[i], editable);
        }
    }

    /**Actualiza la vista de la tabla
     * 
     */
    public void actualizar() {
        try {
            setQuery(SQL);
        } catch (SQLException ex) {
            System.out.println(ex);
        } catch (IllegalStateException ex) {
            System.out.println(ex);
        }
    }

    /**Establece la consulta a la base de datos
     * 
     * @param query consulta 
     * @throws java.sql.SQLException
     * @throws java.lang.IllegalStateException
     */
    public void setQuery(String query)
            throws SQLException, IllegalStateException {
        // ensure database connection is available
        if (!connectedToDatabase) {
            throw new IllegalStateException("Not Connected to Database");
        }
        SQL = query;
        // specify query and execute it
        resultSet = statement.executeQuery(query);
        // obtain meta data for ResultSet
        metaData = resultSet.getMetaData();
        // determine number of rows in ResultSet
        resultSet.last();                   // move to last row
        numberOfRows = resultSet.getRow();  // get row number

    // notify JTable that model has changed
    //fireTableRowsInserted(0, numberOfRows);

    //fireTableStructureChanged();

    } // end method setQuery

    /**Cierra la conexion a la base de datos
     * 
     */
    public void disconnectFromDatabase() {
        if (!connectedToDatabase) {
            return;
        }

        // close Statement and Connection            
        try {
            statement.close();
        //connection.close();
        } // end try                                 
        catch (SQLException sqlException) {
            sqlException.printStackTrace();
        } // end catch                               
        finally // update database connection status
        {
            connectedToDatabase = false;
        } // end finally                             
    } // end method disconnectFromDatabase          
} // end class ResultSetTableModel

